Flutter PopScope 返回拦截
PopScope 组件在 Flutter 中的作用是控制和管理系统的返回手势(back gesture),尤其是在应用需要特定逻辑处理返回事件时非常有用。
示例
该 Demo 来自于 PopScope class 手册,页面内包含一个 Go back'
按钮,点击按钮会弹出一个对话框(_showBackDialog
),问是否退出页面。
如果点击不退出('Nevermind'
),也会执行一次 Pop 操作,其作用是关闭对话框自身。
如果点击确认退出('Leave'
),则执行两次 Pop 操作,第一次为退出 Dialog,第二次为退出页面。
重点是看到 TextButton 外有 PopScope 包裹。它会拦截返回事件,并再一次弹出对话框(_showBackDialog
),进行二次确认。
class _PageTwoState extends State<_PageTwo> {
void _showBackDialog() {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Are you sure?'),
content: const Text(
'Are you sure you want to leave this page?',
),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Nevermind'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Leave'),
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Page Two'),
PopScope(
canPop: false,
onPopInvoked: (bool didPop) {
if (didPop) {
return;
}
_showBackDialog();
},
child: TextButton(
onPressed: () {
_showBackDialog();
},
child: const Text('Go back'),
),
),
],
),
),
);
}
}
属性
PopScope 支持如下属性:
-
canPop 否允许当前路由响应系统返回手势。
- 默认为
true
,这意味着后退手势照常发生。 - 如果设置为
false
,则禁止当前路由的返回操作,通常用于需要用户确认或完成某些操作之前不允许返回的场景。
- 默认为
-
onPopInvoked:后退手势回调
- 所有返回事件都会触发这个回调,即使被拦截,也会调用 onPopInvoked
- 通过回调参数 didPop,表示这次返回事件是否会导致真正的路由返回
- 比如,canPop 为 false 时,将不响应系统返回手势。但是,让系统返回手势发生时,尽管不响应返回行为,但 onPopInvoked 回调依然会调用,并且此时 didPop 值为 false
什么样的路由返回属于被拦截的呢?可以通过 Route.popDisposition
为 false 进行判断。
PopEntry 接口
PopScope 是如何实现拦截系统事件的呢?PopScope 是一个 StatefulWidget,核心实现位于状态(_PopScopeState
)类中。
首先 _PopScopeState
实现了 PopEntry
接口。
在 Flutter 中,PopEntry
接口为 ModalRoute 提供了一种机制,用于监听和控制返回操作。这个接口主要关注两个方面:
- 捕获返回事件的回调
- 控制是否允许返回操作发生
该接口实现如下:
abstract class PopEntry {
/// {@macro flutter.widgets.PopScope.onPopInvoked}
PopInvokedCallback? get onPopInvoked;
/// {@macro flutter.widgets.PopScope.canPop}
ValueListenable<bool> get canPopNotifier;
@override
String toString() {
return 'PopEntry canPop: ${canPopNotifier.value}, onPopInvoked: $onPopInvoked';
}
}
其中:
-
onPopInvoked
:- 这是一个回调函数,用于通知监听者返回操作已被触发。
onPopInvoked
的类型是PopInvokedCallback
,它接受一个bool
类型的参数didPop
,指示返回操作是否成功完成。
-
canPopNotifier
:- 这是一个
ValueListenable<bool>
类型的对象,用于控制是否允许返回操作。 - 其值为
true
时,允许返回操作;为false
时,则阻止返回操作。
- 这是一个
_PopScopeState
基于 PopEntry
的实现
_PopScopeState
实现了 PopEntry
接口,使得 PopScope
组件能够具体执行对返回操作的监听和控制。
注册
在 _PopScopeState
的生命周期内,当依赖的环境(通常是 ModalRoute
)改变时(例如,当组件被插入到树中时),它会注册自己到最近的 ModalRoute
上。这通过调用 ModalRoute.of(context).registerPopEntry(this)
实现。
具体实现如下:
@override
void didChangeDependencies() {
super.didChangeDependencies();
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
if (nextRoute != _route) {
_route?.unregisterPopEntry(this);
_route = nextRoute;
_route?.registerPopEntry(this);
}
}
我之前有一个疑问:==PopScope 只是一个组件,它为什么能够拦截到系统返回手势?==看到这里我明白了。原来,它是通过 ModalRoute.of(context)
获取到当前页面路由,将自己作为接口实现注册进去了。
解注册
当组件被从树中移除时,它会从 ModalRoute
注销自己,确保不再接收或干预返回操作:
@override
void dispose() {
_route?.unregisterPopEntry(this);
canPopNotifier.dispose();
super.dispose();
}
处理返回操作
当返回手势或命令触发时,如果 canPop
是 true
,ModalRoute
会尝试执行返回操作,并调用 onPopInvoked
回调,参数 didPop
为 true
。
如果 canPop
是 false
,则不会执行返回操作,但仍会调用 onPopInvoked
回调,此时 didPop
为 false
,表示返回被阻止。
WillPopScope(废弃)
WillPopScope 组件已经在 v3.12.0-1.0.pre
中被废弃了。
WillPopScope 与 PopScope 的接口并不相同。
WillPopScope 只拦截返回手势,与 Navigator.pop
没有关系!
WillPopScope 原理
WillPopScope 也是一个 StatefulWidget,与 PopScope 不同之处在于,WillPopScope 没有实现 PopEntry 接口,而是通过 ModalRoute 的 addScopedWillPopCallback 方法注册回调。
注册位于 didChangeDependencies,当添加到组件树、或者路哟变更时,及时与 _route
绑定,对陈旧的注册及时解绑:
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.onWillPop != null)
_route?.removeScopedWillPopCallback(widget.onWillPop!);
_route = ModalRoute.of(context);
if (widget.onWillPop != null)
_route?.addScopedWillPopCallback(widget.onWillPop!);
}
其中,onWillPop
是由外部传入的,声明如下:
/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback],
/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
typedef WillPopCallback = Future<bool> Function();
WillPopCallback
会真正阻塞住返回过程,当异步返回 true,页面才会关闭。如果异步返回 true,则拦截住这一次返回操作。
网络资料
本文作者:Maeiee
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!